#include "OP_Rotate.h"

using namespace std;
using namespace CICore;
using namespace CIGeom;
using namespace CIView;

///<************************************************************************************************************>
///<************************************* region CONSTANTS DEFINITION ******************************************>
///<************************************************************************************************************>

/***************************************************************************************************************
************************************** endregion CONSTANTS DEFINITION ******************************************
****************************************************************************************************************/












/// <summary> 
/// Constructor 
/// </summary>
/// <param name="v"> A reference to the vector that contains the four 3D views pointers </param>
CIOps::OP_Rotate::OP_Rotate (vector<CIView::View*>& v)
: Operation (v), feedbackPos (CIView::View::Feedback_Rotation.GetPosition()), feedbackToBeRestored (OperationManager::Origin) {
}

/// <summary> 
/// Destructor 
/// </summary>
CIOps::OP_Rotate::~OP_Rotate() {}









///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary> 
/// Method called to execute directly the operation 
/// </summary>
/// <returns> The operation status </returns>
int CIOps::OP_Rotate::Exec() {
    Vector3D& RotateParams = OperationManager::X_Y_Z_Params;

    if (RotateParams.x == 0 && RotateParams.y == 0 && RotateParams.z == 0) {
        status = ZOMBIE;
        return status;
    }

    int x = (int(RotateParams.x) % 360);
    int y = (int(RotateParams.y) % 360);
    int z = (int(RotateParams.z) % 360);

    //Temporary matrix used to compute the final rotation tranformation
    Matrix toOrigin, toPosition, rotation;     
    rotation.SetRotation (x, y, z);                                                 //The rotation to apply 

    if (OperationManager::CenterOfMass) {
        toPosition.SetTranslation (feedbackPos.x, feedbackPos.y, feedbackPos.z);        //Sets up the translation to the final position            
        toOrigin.SetTranslation (-feedbackPos.x, -feedbackPos.y, -feedbackPos.z);       //Sets up the translation to the origin        
        rotationMatrix = toOrigin * rotation * toPosition;                              //Computes the final matrix for the correct rotation transformation

        scene.ApplyMatrix (rotationMatrix);                                             //Applies the transformation to the selected vertices
    }
    else if (OperationManager::UserPoint) {
        feedbackToBeRestored = true;

        toPosition.SetTranslation (feedbackPos.x, feedbackPos.y, feedbackPos.z);        //Sets up the translation to the final position            
        toOrigin.SetTranslation (-feedbackPos.x, -feedbackPos.y, -feedbackPos.z);       //Sets up the translation to the origin        
        rotationMatrix = toOrigin * rotation * toPosition;                              //Computes the final matrix for the correct rotation transformation

        scene.ApplyMatrix (rotationMatrix);                                             //Applies the transformation to the selected vertices 
    }
    else {
        feedbackToBeRestored = true;
        rotationMatrix = rotation;

        scene.ApplyMatrix (rotationMatrix);                         //Applies the transformation to the selected vertices
        RestoreFeedbackPosition();
    }

    inverseMatrix = rotationMatrix.GetInverse();                //Computes the new inverse matrix (used to undo the transformation)

    UpdateViews();                                              //Updates the 3D views

    status = COMPLETED;
    
    return status;              
}

/// <summary> 
/// Method called to undo the operation 
/// </summary>
void CIOps::OP_Rotate::Undo() {
    status = UNDO;

    scene.ApplyMatrix (inverseMatrix);  //Applies the last inverse matrix

    if (feedbackToBeRestored) 
        RestoreFeedbackPosition();
    

    UpdateViews();                      //Updates the 3D views
}

/// <summary> 
/// Method called to redo the operation 
/// </summary>
void CIOps::OP_Rotate::Redo() {
    status = COMPLETED;

    scene.ApplyMatrix (rotationMatrix);  //Applies the last rotation matrix
    
    if (feedbackToBeRestored) 
        RestoreFeedbackPosition();

    UpdateViews();                          //Updates the 3D views
}

/// <summary> 
/// Mouse Pressed event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse press event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Pressed event </param>
void CIOps::OP_Rotate::MousePressEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();

    if (OperationManager::UserPoint) {
        CIView::View::Feedback_Rotation.SelectAll();
        selected = FeedbackRotation::ALL_AXES;
    }    
    else
        selected = view->SelectFeedbackRotationAxes (x, y);         //Selects the axes according to the mouse click2D coordinates
    
    view->WindowToWorldCoordinates (&x, &y);                     //Unproject the mouse cursor coordinates

    click.x = x;
    click.y = y;
    
    
    Orientation& cameraOrient = view->GetCameraOrientation();

    if (OperationManager::UserPoint) {
        feedbackToBeRestored = true;

        //Computing the feedback position according to the user click coordinates
        switch (view->GetCameraType()) {
            case Camera::TOP:
                feedbackPos.x = x;
                feedbackPos.z = y;                
                click.y -= FeedbackRotation::CIRCLE_RADIUS;
                break;
            
            case Camera::FRONT:
                feedbackPos.x = x;
                feedbackPos.y = -y;
                click.y -= FeedbackRotation::CIRCLE_RADIUS;
                break;
            
            case Camera::RIGHT:  
                feedbackPos.z = -x;          
                feedbackPos.y = -y;
                click.y -= FeedbackRotation::CIRCLE_RADIUS;
                break;
             
            case Camera::USER:
                //TODO
                //la z corrisponde (pi� o meno) alla y quando camera->orientation.x > 20 e < 150
                //la z corrisponde (pi� o meno) alla x quando camera->orientation.y > 20 e < 150
                //si pu� fare una specia di interpolazione
                float c = cos (float (cameraOrient.y)) * sin (float (cameraOrient.x));
                float s = sin (float (cameraOrient.y)) * cos (float (cameraOrient.x));

                feedbackPos.x = x;
                feedbackPos.y = -y;
                feedbackPos.z = (-x*s + y*c);
                click.y -= FeedbackRotation::CIRCLE_RADIUS;
                break;
        }
    }

    //Setting the initial rotation angle (in radians) to show the feedback
    CIView::View::Feedback_Rotation.SetRotationAngle (0);
    
    UpdateViews();                                              //Updates the 3D views
}

/// <summary> 
/// Mouse Released event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse release event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Released event </param>
void CIOps::OP_Rotate::MouseReleaseEvent (CIView::View* view, QMouseEvent* e) {
    if (cursor.x != 0 || cursor.y != 0)
        status = COMPLETED;
    else
        status = ZOMBIE;

    CIView::View::Feedback_Rotation.DeselectAll();
    UpdateViews();                                       //Updates the 3D views
}

/// <summary> 
/// Mouse Moved event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse move event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Moved event </param>
void CIOps::OP_Rotate::MouseMoveEvent (CIView::View* view, QMouseEvent* e) {

    float x = e->x();    
    float y = e->y();

    view->WindowToWorldCoordinates (&x, &y);                    //Unproject the mouse cursor coordinates

    cursor.x = x;
    cursor.y = y;

    Orientation& cameraOrient = view->GetCameraOrientation();   //The current camera orientation
    Point& vrp = view->GetCameraVrp();                          //The current view reference point

    Point rotationOrigin = feedbackPos;
        


    //Computing the rotation angle          
    Vector3D origin_to_click (click.x, click.y);                //Vector between the feedback position and the mouse click point (in world coordinates)              
    Vector3D origin_to_cursor (cursor.x, cursor.y);             //Vector between the feedback position and the current mouse cursor position (in world coordinates)              

    //Translating the the vectors to the feedback position
    switch (view->GetCameraType()) {
        case Camera::TOP:
            origin_to_click.x -= rotationOrigin.x;
            origin_to_click.y -= rotationOrigin.z;
            
            origin_to_cursor.x -= rotationOrigin.x;
            origin_to_cursor.y -= rotationOrigin.z;
            break;

        case Camera::FRONT:
            origin_to_click.x -= rotationOrigin.x;
            origin_to_click.y += rotationOrigin.y;

            origin_to_cursor.x -= rotationOrigin.x;
            origin_to_cursor.y += rotationOrigin.y;
            break;

        case Camera::RIGHT:
            origin_to_click.x += rotationOrigin.z;
            origin_to_click.y += rotationOrigin.y;
            
            origin_to_cursor.x += rotationOrigin.z;
            origin_to_cursor.y += rotationOrigin.y;
            break;
         
        case Camera::USER:
            //TODO
            //la z corrisponde (pi� o meno) alla y quando camera->orientation.x > 20 e < 150
            //la z corrisponde (pi� o meno) alla x quando camera->orientation.y > 20 e < 150
            //si pu� fare una specie di interpolazione
            float c = cos (float (cameraOrient.x)) * rotationOrigin.z;
            float s = sin (float (cameraOrient.y)) * rotationOrigin.z;
            origin_to_click.x -= rotationOrigin.x + s;
            origin_to_click.y += rotationOrigin.y + c;
            
            origin_to_cursor.x -= rotationOrigin.x + s;
            origin_to_cursor.y += rotationOrigin.y + c;

            //TODO -> change the sign of the vectors values according to the camera orientation
            break;
    }
    float length = origin_to_click.Length();            //Length of the vector

    //origin_to_click represents the vector that connects the origin to the mouse cursor coordinates 
    //so, origin_to_click.x represents the cosin value of the angle between the x-axis and the origin_to_click vector, multiplied by length
    //and origin_to_click.y represents the sin value of the angle between the x-axis and the origin_to_click vector, multiplied by length
    //so we can calculate the normalized cosin and sin values
    float cos_click = origin_to_click.x / length;      //Normalizes (into [0,1]) the cosin of the angle between the x-axis and the origin_to_click vector 
    float sin_click = -origin_to_click.y / length;     //Normalizes (into [0,1]) the sin of the angle between the x-axis and the origin_to_click vector 

    
    length = origin_to_cursor.Length();                 //Length of the vector

    //origin_to_cursor represents the vector that connects 
    //the origin to the mouse cursor coordinates 
    //so, origin_to_cursor.x represents the cosin value 
    //of the angle between the x-axis and the origin_to_cursor 
    //vector, multiplied by length and origin_to_cursor.y 
    //represents the sin value of the angle between the x-axis and 
    //the origin_to_cursor vector, multiplied by length
    //so we can calculate the normalized cosin and sin values    
    float cos_cursor =  origin_to_cursor.x / length;     //Normalizes (into [0,1]) the cosin of the angle between the x-axis and the origin_to_cursor vector 
    float sin_cursor = -origin_to_cursor.y / length;     //Normalizes (into [0,1]) the sin of the angle between the x-axis and the origin_to_cursor vector 

    float angle_click = acos (cos_click);          //Computes the angle between the x-axis and the first mouse click coordinates  

    if (sin_click < 0) 
        angle_click *= -1;    
    
    float angle_cursor = acos (cos_cursor);        //Computes the angle between the x-axis and the current mouse cursor coodinates  

    if (sin_cursor < 0) 
        angle_cursor *= -1; 

    //Computing the final rotation angle 
    float radians = angle_cursor - angle_click;

    float angle = radians * 180.0f / PI; 








    //Temporary matrix used to compute the final rotation tranformation
    Matrix toOrigin, toPosition, rotation;    
    toPosition.SetTranslation (feedbackPos.x, feedbackPos.y, feedbackPos.z);        //Sets up the translation to the final position            
    toOrigin.SetTranslation (-feedbackPos.x, -feedbackPos.y, -feedbackPos.z);       //Sets up the translation to the origin        


    //Setting up the rotation matrix according to the selected axes
    switch (selected) {
        case FeedbackRotation::X_AXIS:  
            rotation.SetRotation (angle, 0, 0);     //Sets up the rotation transformation            
            break;

        case FeedbackRotation::Y_AXIS:  
            rotation.SetRotation (0, angle, 0);     //Sets up the rotation transformation
            break;
            
        case FeedbackRotation::Z_AXIS:  
            rotation.SetRotation (0, 0, angle);     //Sets up the rotation transformation
            break;

        case FeedbackRotation::ALL_AXES:          
            //Computing the rotation angle according to the current camera type
            switch (view->GetCameraType()) {
                case Camera::TOP:                    
                    rotation.SetRotation (0, angle, 0);     //Sets up the rotation transformation
                    break;
                
                case Camera::FRONT:                    
                    rotation.SetRotation (0, 0, angle);     //Sets up the rotation transformation
                    break;
                
                case Camera::RIGHT:                            
                    rotation.SetRotation (angle, 0, 0);     //Sets up the rotation transformation
                    break;

                case Camera::USER:                        
                    //Setting up the rotation on an arbitrary axis
                    Matrix x1, y1, x2, y2, z;
                    x1.SetRotation (cameraOrient.x, 0, 0);    
                    y1.SetRotation (0, cameraOrient.y, 0);
                    z.SetRotation (0, 0, angle);           
                    y2.SetRotation (0, -cameraOrient.y, 0);         
                    x2.SetRotation (-cameraOrient.x, 0, 0);
                    
                    rotation = x1 * y1 * z * y2 * x2;     //Sets up the rotation transformation
                    break;
            }
            break;
    }
    rotationMatrix = toOrigin * rotation * toPosition;          //Computes the final matrix for the correct rotation transformation

    Matrix toApply;
    toApply = inverseMatrix*rotationMatrix;              //The matrix to apply for the transformation has to consider the previous transformation

    scene.ApplyMatrix (toApply);                                //Applies the transformation to the selected vertices
       
    //Setting the rotation angle (in radians) to show the feedback
    CIView::View::Feedback_Rotation.SetRotationAngle (radians);

    inverseMatrix = rotationMatrix.GetInverse();                //Computes the new inverse matrix (used to undo the transformation)

    UpdateViews();                                              //Updates the 3D views
}

/// <summary> 
/// Wheel event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse wheel event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Wheel event </param>
void CIOps::OP_Rotate::WheelEvent (CIView::View* view, QWheelEvent* e) {
}
/***************************************************************************************************************
***************************************** endregion Utility Methods ********************************************
****************************************************************************************************************/
